#include "StdAfx.h"
#include "GraphicsController.h"
#include "D3DCameraImpl.h"
#include "RenderableObject.h"
#include "Light.h"
#include "SkyBox.h"
#include <stdexcept>
#include <string>


/////////////////////////////////////////////////////////////////////////////

CGraphicsController::CGraphicsController(IDirect3DDevice9* d3Device, 
                                         D3DPRESENT_PARAMETERS creationParams,
                                         D3DCAPS9 caps,
                                         float frontBufferAspect,
                                         bool hardwareTLOn,
                                         D3DFORMAT optimalTextureFormat)
      : m_d3Device(d3Device),
      m_creationParams(creationParams),
      m_caps(caps),
      m_deviceLost(false),
      m_hardwareTLOn(hardwareTLOn),
      m_optimalTextureFormat(optimalTextureFormat),
      m_skyBox(NULL)
{
   initRendererState();
}

/////////////////////////////////////////////////////////////////////////////

CGraphicsController::~CGraphicsController(void)
{
   for (std::list<CD3DCameraImpl*>::iterator it = m_cameras.begin(); 
         it != m_cameras.end(); it++)
   {
      delete *it;
   }
   m_cameras.clear();

   for (std::list<CLight*>::iterator it = m_lights.begin();
         it != m_lights.end(); it++)
   {
      delete *it;
   }
   m_lights.clear();

   if (m_skyBox != NULL)
   {
      delete m_skyBox;
      m_skyBox = NULL;
   }

   if (m_d3Device != NULL)
   {
      m_d3Device->Release();
      m_d3Device = NULL;
   }
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::setTextureDirectoryPath(const std::wstring& path)
{
   m_textureDirPath = path;
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::initRendererState()
{
    // Setup our D3D Device initial states
    m_d3Device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
    m_d3Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
    m_d3Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
    m_d3Device->SetRenderState(D3DRS_LIGHTING, TRUE);
    m_d3Device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
    m_d3Device->SetRenderState(D3DRS_SPECULARENABLE, TRUE);

    m_d3Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    m_d3Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    m_d3Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

    m_d3Device->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    m_d3Device->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    m_d3Device->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::resizeViewport(unsigned int width, unsigned int height)
{
   if ((m_d3Device) && (m_creationParams.Windowed == TRUE))
   {
      // Store new sizes
      m_creationParams.BackBufferWidth = width;
      m_creationParams.BackBufferHeight = height;
   
      for (std::list<CD3DCameraImpl*>::iterator it = m_cameras.begin(); 
            it != m_cameras.end(); it++)
      {
         CD3DCameraImpl* camera = *it;
         camera->setViewport(0, 0, width, height, 1.01f, 1000.0f);
         camera->updateProjectionMtx();
      }

      // Reset the device
      m_d3Device->Reset(&m_creationParams);
      initRendererState();
   }
}

/////////////////////////////////////////////////////////////////////////////

bool CGraphicsController::isGraphicsSystemReady()
{
   if ((m_deviceLost == true) && (tryRecoveringLostDevice() == false))
   {
      return false;
   }

   return true;
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::render(const std::vector<IRenderableObject*>& renderableObjects)
{
   for (std::list<CLight*>::iterator it = m_lights.begin();
         it != m_lights.end(); it++)
   {
      CLight* light = *it;
      light->update();
   }

   for (std::list<CD3DCameraImpl*>::iterator it = m_cameras.begin(); 
         it != m_cameras.end(); it++)
   {
      CD3DCameraImpl* camera = *it;

      camera->render(renderableObjects);
   }
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::setSkyBox(const std::wstring& topFaceTexFileName,
                                    const std::wstring& bottomFaceTexFileName,
                                    const std::wstring& frontFaceTexFileName,
                                    const std::wstring& backFaceTexFileName,
                                    const std::wstring& leftFaceTexFileName,
                                    const std::wstring& rightFaceTexFileName)
{
   if (m_skyBox != NULL)
   {
      delete m_skyBox;
   }

   m_skyBox = new CSkyBox(*this, 
                          topFaceTexFileName,
                          bottomFaceTexFileName,
                          frontFaceTexFileName,
                          backFaceTexFileName,
                          leftFaceTexFileName,
                          rightFaceTexFileName);

   for (std::list<CD3DCameraImpl*>::iterator it = m_cameras.begin(); 
         it != m_cameras.end(); it++)
   {
      CD3DCameraImpl* camera = *it;
      camera->setSkyBox(*m_skyBox);
   }
}

/////////////////////////////////////////////////////////////////////////////

void CGraphicsController::present()
{
   if (FAILED(m_d3Device->Present(NULL, NULL, NULL, NULL)))
   {
      m_deviceLost = true;
   }
}

/////////////////////////////////////////////////////////////////////////////

bool CGraphicsController::tryRecoveringLostDevice()
{
   HRESULT deviceStatus = m_d3Device->TestCooperativeLevel();

   switch(deviceStatus)
   {
   case D3D_OK:
      {
         m_deviceLost = false;
         return true;
      }

   case D3DERR_DEVICENOTRESET:
      {
         HRESULT resettingResult = m_d3Device->Reset(&m_creationParams);

         if (SUCCEEDED(resettingResult))
         {
            initRendererState();
            m_deviceLost = false;
            return true;
         }
         else
         {
            return false;
         }
      }

   default:
      return false;
   }
}

/////////////////////////////////////////////////////////////////////////////

IDirect3DTexture9* CGraphicsController::loadTextureFromFile(const std::wstring& fileName, 
                                                            D3DXIMAGE_INFO* imageInfo, 
                                                            D3DCOLOR colorKey)
{
   IDirect3DTexture9* texture = NULL;

   std::wstring fullFileName = m_textureDirPath + fileName;

   HRESULT res = D3DXCreateTextureFromFileEx(m_d3Device, fullFileName.c_str(), D3DX_DEFAULT, D3DX_DEFAULT,
                               D3DX_DEFAULT, 0, m_optimalTextureFormat, D3DPOOL_MANAGED,
                               D3DX_DEFAULT, D3DX_DEFAULT, colorKey, imageInfo, NULL, &texture);

   if (FAILED(res))
   {
      throw std::logic_error(
         std::string("Cannot load a texture from a file"));
   }

   return texture;
}

/////////////////////////////////////////////////////////////////////////////

IDirect3DVertexBuffer9* CGraphicsController::createVertexBuffer(UINT length, 
                                                               DWORD usageFlags, 
                                                               DWORD fvf, 
                                                               D3DPOOL memoryPool)
{
   if (!m_hardwareTLOn) usageFlags |= D3DUSAGE_SOFTWAREPROCESSING;

   IDirect3DVertexBuffer9* vertexBuffer = NULL;
   HRESULT res = m_d3Device->CreateVertexBuffer(length,
                                  usageFlags,
                                  fvf,
                                  memoryPool,
                                  &vertexBuffer,
                                  NULL);

   if (FAILED(res))
   {
      throw std::logic_error(
         std::string("Cannot create a vertex buffer"));
   }

   return vertexBuffer;
}

/////////////////////////////////////////////////////////////////////////////

IDirect3DIndexBuffer9* CGraphicsController::createIndexBuffer(UINT length, 
                                                             DWORD usageFlags, 
                                                             D3DFORMAT format, 
                                                             D3DPOOL memoryPool)
{
   if (!m_hardwareTLOn) usageFlags |= D3DUSAGE_SOFTWAREPROCESSING;

   IDirect3DIndexBuffer9* indexBuffer = NULL;

   HRESULT res = m_d3Device->CreateIndexBuffer(length,
                                  usageFlags,
                                  format,
                                  memoryPool,
                                  &indexBuffer,
                                  NULL);

   if (FAILED(res))
   {
      throw std::logic_error(
         std::string("Cannot create an index buffer"));
   }

   return indexBuffer;
}

/////////////////////////////////////////////////////////////////////////////

ICameraImpl& CGraphicsController::createCamera()
{
   CD3DCameraImpl* camera = new CD3DCameraImpl(*m_d3Device);
   m_cameras.push_back(camera);

   if (m_skyBox != NULL)
   {
      camera->setSkyBox(*m_skyBox);
   }

   return *camera;
}

/////////////////////////////////////////////////////////////////////////////

CLight& CGraphicsController::createDynamicLight(D3DLIGHTTYPE lightType)
{
   CLight* light = new CLight(*m_d3Device, lightType);
   m_lights.push_back(light);

   return *light;
}

/////////////////////////////////////////////////////////////////////////////

IDirect3DDevice9& CGraphicsController::getD3Device()
{
   return *m_d3Device;
}

/////////////////////////////////////////////////////////////////////////////

UINT CGraphicsController::getMaxLightsCount() const
{
   return m_caps.MaxActiveLights;
}

/////////////////////////////////////////////////////////////////////////////

UINT CGraphicsController::getSimultaneouslyRenderedTexturesCount() const
{
   UINT count = m_caps.MaxSimultaneousTextures;
   if (count > m_caps.MaxTextureBlendStages) count = m_caps.MaxTextureBlendStages;

   return count;
}

/////////////////////////////////////////////////////////////////////////////
